package oop.ex3.filemanager;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyFileManager {

	private final static String MSG_FILE_EXISTS = "File already exists";
	private final static String MSG_FILE_DOWNLOADED = "File Downloaded Successfully from {0}:{1}";
	private final static String MSG_FILE_DOWNLOAD_FAILED = "Downloading failed";
	private final static String MSG_FILE_DELETED = "Removing Done";
	private final static String MSG_DELETING_ABSENT_FILE = "It is impossible to delete an absent file";
	private final static String MSG_RENAMING_ABSENT_FILE = "It is impossible to rename an absent file";
	private final static String MSG_RENAMING_WITH_AN_EXISTING_NAME = 
		"It is illegal to use an existing file name as a new name";
	private final static String MSG_FILE_RENAMED = "Renaming Done";
	private final static String MSG_QUIT = "Bye-bye!";
	

	//data members:
	private List<NameServer> _nameServers = new ArrayList<NameServer>();
	private List<File> _dataBase = new ArrayList<File>();
	private int _port;
	private File _directoryPath;
	private UploadRequestThread _uploadRequestThread;
	
	public static void main(String[] args) throws IOException {

		File serverList = new File (args[0]);
		File directory = new File (args[1]);
		int port = Integer.parseInt(args[2]);
		
		
		MyFileManager newFileManager = new MyFileManager(serverList, directory, port);

		newFileManager.start();

		//initializing the user input thread:
		FMUserInterface.listenToUser(newFileManager);
	}


	private void start() {
		for(NameServer nameServer : _nameServers) {
			nameServer.presentFileManager(_dataBase, _nameServers);
		}
		_uploadRequestThread.start();
	}

	//Constructor:
	public MyFileManager(File serverList, File directory, int port) throws IOException{
		_port = port;
		_directoryPath = directory;

		//creating the server list:
		parseNameServers(serverList);

		//creating a list of files:
		createDataBase(directory);
		
		//creating a thread that 'listens' to upload requests:
		_uploadRequestThread = new UploadRequestThread(_port, _directoryPath);

	}


	//creating the manager's data base:
	private void createDataBase(File directory) {
		String[] files = directory.list();
		for(String file : files){
			_dataBase.add(new File(file));
		}

	}


	//creating the list of known servers from the initial file:
	private void parseNameServers(File serverList) throws IOException{
		FileReader input = new FileReader(serverList);
		BufferedReader reader = new BufferedReader(input);

		String line = reader.readLine();
		String[] lineComponents;

		while (line!=null){
			lineComponents = line.split("%");
			_nameServers.add(new NameServer(_port, lineComponents[0], Integer.parseInt(lineComponents[1])));
			line = reader.readLine();
		}

	}


	//for the DIR command:
	public List<File> listFiles(){
		List<File> dataBaseCopy = new ArrayList<File>(_dataBase);
		Collections.sort(dataBaseCopy);
		return dataBaseCopy;

	}
	//for the DIRSERVERS command:
	public List<NameServer> listServers(){
		return _nameServers;
	}



	/**
	 * a public method that returns true if a file is in it's database, and false otherwise
	 * @param wantedFile the file to be searched in the database
	 * @return true if wantedFile is in this's database, false otherwise
	 */
	public boolean isFileInDataBase(String wantedFile){
		for(File file : _dataBase){
			if(file.getName().equals(wantedFile)){
				return true;
			}
		}
		return false;
	}


	//REMOVE command:
	public String remove(String fileToRemove) {
		String status;
		if (!isFileInDataBase(fileToRemove)){
			status = MSG_DELETING_ABSENT_FILE;
		}else{
			//TODO If the file is being uploaded to some other FileManagers, wait for the all uploads to finish;
			_uploadRequestThread.checkIfFileIsUploaded(fileToRemove);
			for (File file : _dataBase){
				if(file.getName().equals(fileToRemove)){
					File tempFile = new File(_directoryPath.getAbsolutePath()+"\\"+ fileToRemove);
					tempFile.delete();
					_dataBase.remove(file);
					break;
				}
			}
			for (NameServer nameServer : _nameServers) {
				nameServer.notifyRemovedFile(fileToRemove);
			}
			status = MSG_FILE_DELETED;
		}
		return status;
	}

	//ADD command:
	public String add(String wantedFile) {
		String status;
		if (isFileInDataBase(wantedFile)) {
			status = MSG_FILE_EXISTS;
		} else {
			boolean successfulDownload = false;
			List<NameServer> nameServersAlreadyChecked = new ArrayList<NameServer>();
			while (nameServersAlreadyChecked.size() < _nameServers.size()) {
				for(NameServer nameServer : _nameServers){
					if (!nameServersAlreadyChecked.contains(nameServer)) {
						nameServersAlreadyChecked.add(nameServer);
						List<RemoteFileManager> fileManagersWithFile = nameServer.retrieveFileManagersWithFile(wantedFile);
						for(RemoteFileManager fileManager : fileManagersWithFile) {
							successfulDownload = fileManager.downloadFile(_port,wantedFile, _directoryPath);
							if(successfulDownload){
								_dataBase.add(new File(_directoryPath, wantedFile));
								for (NameServer nameServerToNotify : _nameServers) {
									nameServerToNotify.notifyAddedFile(wantedFile);
								}
								status = MessageFormat.format(MSG_FILE_DOWNLOADED, fileManager.getHost(), fileManager.getPort());
								return status;
							}
						}
					}
				}
				for(NameServer nameServer : _nameServers){
					List<NameServer> newNameServers = nameServer.requestMoreServers();
					for (NameServer newServer : newNameServers) {
						if (!_nameServers.contains(newServer)) {
							_nameServers.add(newServer);
						}
					}
				}
			}
			status = MSG_FILE_DOWNLOAD_FAILED;
		}
		return status;
	}

	
	//rename command:
	public String rename(String oldName, String newName) {
		String status;
		if(!isFileInDataBase(oldName)){
			status = MSG_DELETING_ABSENT_FILE;
		}else if(isFileInDataBase(newName)){
			status = MSG_RENAMING_WITH_AN_EXISTING_NAME;
		}else{
			_dataBase.remove(_dataBase.indexOf(new File(oldName)));
			_uploadRequestThread.checkIfFileIsUploaded(oldName);
			File fileToRename = new File(_directoryPath.getAbsolutePath()+"\\"+ oldName);
			File newFileName = new File(_directoryPath.getName() + "\\" + newName);
			fileToRename.renameTo(newFileName);
			_dataBase.add(newFileName);
			status = MSG_FILE_RENAMED;
			for(NameServer nameServer : _nameServers){
				nameServer.notifyRemovedFile(oldName);
				nameServer.notifyAddedFile(newName);
			}
		}
		return status;
	}

	
	
	
	
	//the DIR ALL FILES command:
	public List<String> getAllFilesInSystem() {
		List<NameServer> nameServersAlreadyChecked = new ArrayList<NameServer>();
		List<NameServer> allNameServers = new ArrayList<NameServer>(_nameServers);
		List<String> allFiles = new ArrayList<String>();
		while(nameServersAlreadyChecked.size()<allNameServers.size()){
			for(NameServer nameServer : allNameServers){
				if (!nameServersAlreadyChecked.contains(nameServer)) {
					nameServersAlreadyChecked.add(nameServer);
					List<String> knownFilesToThisServer = nameServer.getAllFiles();
					for(String fileName : knownFilesToThisServer){
						if(!allFiles.contains(fileName)){
							allFiles.add(fileName);
						}
					}
					List<NameServer> newNameServers = nameServer.requestMoreServers();
					for (NameServer newServer : newNameServers) {
						if (!allNameServers.contains(newServer)) {
							allNameServers.add(newServer);
						}
					}
				}
			}
		}
		Collections.sort(allFiles);
		return allFiles;
	}

	public void addFileToDB(File newFile){
		_dataBase.add(newFile);
	}


	public String Quit() {
		_uploadRequestThread.stopAcceptingUploadRequests();
		_uploadRequestThread.waitForAllUploadsToFinish();
		
		for(NameServer nameServer : _nameServers){
			nameServer.goodbey();
		}
		return MSG_QUIT;
	}


	public void fireAllServers() {
		for(NameServer nameServer : _nameServers) {
			nameServer.goodbey();
		}
		
	}





}
